Skip to content

Conversation

@C-Achard
Copy link

@C-Achard C-Achard commented Jan 30, 2026

Finalizing the PySide6+multi-camera GUI

Further refinement of GUI added in #35 by @arturoptophys

Features

  • Full project file tree refactor for more granularity and ease of use
  • Typed configs
  • Improved settings UX/UI
  • Theme/UI alignment with DLC, icons, colors, etc
  • Improved camera loading UX and validation
  • Many fixes to OpenCV backend
  • Fixes all issues in Pre-release fixes & improvements checklist #37

Also tweaks error handling, UI and UX.


Additional TODOs

  • Documentation overhaul (separate PR?)
  • Comprehensive unit and GUI test suite, >80% coverage (49% current)
    • In particular, video recording coverage is very low currently

arturoptophys and others added 30 commits October 21, 2025 11:22
…modern-python-and-pyqt6

Add Basler and GenTL camera backends for modular capture
…camera-functionality

Rework layout and camera handling controls
…r integration

- Implemented `get_device_count` method in `GenTLCameraBackend` to retrieve the number of GenTL devices detected.
- Added `max_devices` configuration option in `CameraSettings` to limit device probing.
- Introduced `BoundingBoxSettings` for bounding box visualization, integrated into the main GUI.
- Enhanced `DLCLiveProcessor` to accept a processor instance during configuration.
- Updated GUI to support processor selection and auto-recording based on processor commands.
- Refactored camera properties handling and removed deprecated advanced properties editor.
- Improved error handling and logging for processor connections and recording states.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Replaces the nested QHBoxLayout-based stats section with a QGridLayout for improved alignment and spacing of the Camera, DLC Processor, and Recorder status labels. This change simplifies the layout code and ensures better control over column stretching and widget alignment.
Introduces splash screen functionality and displays a logo with version text in the preview area when cameras are not running. Adds new logo and welcome image assets, updates the main window class name to DLCLiveMainWindow, and refactors icon loading and display logic for improved branding and user experience.
Enhances robustness by stopping and removing recorders after write errors, updating user notifications, and refining error messages for device disconnection. Also improves thread finalization and error handling in the VideoRecorder's writer loop.
Introduces persistent storage of the last used model path using QSettings and validates model files with a new utility function. Enhances camera selection logic to handle dynamic camera availability, updates the inference camera dropdown accordingly, and improves error handling and logging. Adds dlclivegui/utils.py with is_model_file for model file validation.
Refactors CameraConfigDialog to use a working copy of settings, adds UI helpers for backend-specific controls, and improves preview FPS reconciliation. Enhances camera backend probing in factory.py with quick_ping and sanitized settings, and exposes actual_fps and actual_resolution in OpenCVCameraBackend. Updates main window to skip camera validation while the config dialog is active.
Enhances the CameraConfigDialog to better handle Enter key events, apply settings more robustly, and improve preview state management. Refines FPS reconciliation logic for OpenCV cameras, ensuring user-requested FPS is not overwritten unless actual FPS is measurable. In the OpenCV backend, replaces LOG with logger, adds more detailed debug logging, and clarifies FPS and resolution handling, including improved logging for property setting and codec negotiation.
Major refactor of the GUI codebase: moves main window, camera config dialog, and theme logic into a new gui/ subpackage; introduces a RecordingManager for multi-camera recording; modularizes display, stats, and utility functions into utils/; moves service logic (DLC processor, video recorder, multi-camera controller) into services/; updates imports and usage throughout. This improves maintainability, separation of concerns, and code clarity.
Corrects the dlclivegui script entry point in pyproject.toml to point to dlclivegui:main. Cleans up comments and redundant code in splash screen logic in main.py. Updates a status message in camera_config_dialog.py for clarity.
Camera backends have been moved to a dedicated 'backends' subpackage and now use a registry with decorators for dynamic registration. Introduced config_adapters for flexible CameraSettings handling, and added Pydantic-based config models in utils/config_models.py for validation and conversion. The camera factory and processor logic now accept both dataclass and Pydantic models, improving flexibility and type safety. Also added a QtSettingsStore utility for persistent settings, and updated dependencies to include pydantic.
Introduce comprehensive test coverage for camera adapters, factory, and fake backends, as well as the DLC processor service. Includes fixtures and test doubles for isolated testing, and covers configuration, initialization, frame processing, error handling, and statistics computation.
Enhanced MultiCameraController to accept various camera settings formats (dataclasses, dicts, or pydantic models) and normalize them. Added a utility for converting settings, updated worker and controller logic, and introduced a new test for mixed input types. Also added a FakeBackend and patch_factory fixture for testing.
Added unit tests to verify frame rotation and cropping behavior, as well as handling of camera initialization failures in MultiCameraController. These tests improve coverage for edge cases and error handling.
Avoid calling queue.task_done() for the shutdown sentinel in DLCLiveProcessor worker loop and guard against ValueError if task_done is called unexpectedly. This prevents erroneous task accounting when the sentinel is used to stop the thread.

Add GUI end-to-end tests and test fixtures: introduce tests/services/gui/conftest.py to provide a headless DLCLiveMainWindow fixture, patch CameraFactory and DLCLive to use test doubles, and add tests/services/gui/test_e2e.py which exercises preview rendering and inference flow. Minor test updates: supply a FakeBackend import for camera factory tests and add stop() stubs to fake backend implementations used in tests.
Migrate config dataclasses to Pydantic models and add lazy backend discovery.

Key changes:
- Replace dataclass-based CameraSettings/ApplicationSettings/etc. with Pydantic models in utils/config_models.py (CameraSettingsModel, ApplicationSettingsModel, MultiCameraSettingsModel, etc.). Added model helpers (from_dict/to_dict, load/save, output_path, writegear_options, convenience methods).
- Update camera backend API to accept CameraSettingsModel and tighten abstract methods (raise NotImplementedError by default).
- Rename and expose internal backend registry as _BACKEND_REGISTRY and update factory code to reference it.
- Implement lazy backend loading in cameras.factory: import backend packages/modules via importlib/pkgutil on first use so third-party or on-disk backend packages can register themselves via @register_backend. Added guard to raise if no backends are registered in GUI dialog.
- Adapt GUI (camera_config_dialog.py, main_window.py) to use the new models and validate/coerce form data via Pydantic models. Added form->model builder and updated preview/reconcile logic to operate on models.
- Add dlclivegui/cameras/backends/__init__.py to import built-in backend modules.
- Add tests/cameras/test_backend_discovery.py to verify lazy discovery, detection and creation of a temporarily installed test backend package.

Notes/compatibility:
- Public APIs that previously accepted dataclasses now expect the new *Model types (e.g. CameraSettingsModel, ApplicationSettingsModel). This is a breaking change; conversion helpers/compat shims were added where needed in the GUI.
- Factory now ensures backends are imported before listing/using them, enabling plugin/backends discovered on disk.
- Small behavioral change: CameraBackend methods now explicitly raise NotImplementedError unless overridden.
Replace legacy dataclass config usage with Pydantic models across cameras and DLC services. CameraSettingsModel and DLCProcessorSettingsModel are now used in controllers, processors, recorders and tests; adapters and ensure_dc_camera/list_of_dc_cameras utilities were removed/simplified. CameraBackend.stop is now an optional no-op by default. Add CameraSettingsModel helpers (from_dict, from_defaults, apply_defaults) and relax backend typing/default. Update tests and imports accordingly, and remove the obsolete test_adapters.py file.
Add conversion helper and validation for dynamic crop settings, and sync camera UI to models.

- utils/config_models.py: Add DynamicCropModel.to_tuple() to expose (enabled, margin, max_missing_frames).
- services/dlc_processor.py: Accept DynamicCropModel-like objects for dynamic settings by attempting .to_tuple(); validate format and raise a clear error on invalid data before unpacking.
- gui/camera_config_dialog.py: Automatically select the first available camera backend after populating the backend list, add _write_form_to_cam() to write UI control values back into a CameraSettingsModel, and update the preview reopen call to match the changed _needs_preview_reopen signature.

These changes improve robustness when dynamic crop settings are provided as model objects and ensure the camera configuration UI persists and selects a usable backend by default.
Major refactor of DLCLiveProcessor: remove legacy settings normalization, simplify configure(), start worker on first enqueued frame, always enqueue if queue exists, and eliminate sentinel-based shutdown. Introduce _timed_processor contextmanager and a dedicated _process_frame() to separate GPU inference, optional processor overhead timing, signal emission, and stats updates; add frame_processed signal and improve queue/task_done handling and stop/drain logic. Small change in GUI main window: only stop inference on camera error when no DLC camera remains. Add unit and end-to-end tests for camera config dialog and extend/rename several test modules to cover processor behavior and queue accounting.
@C-Achard C-Achard self-assigned this Jan 30, 2026
@C-Achard C-Achard added the enhancement New feature or request label Jan 30, 2026
Enhance model-path handling and UI file dialog behavior, and refactor tiling/overlay math for consistent scaling.

- dlclivegui/config.py: add robust path normalization, separate last-model-file and last-model-dir storage, methods to suggest start directory and preselected file, and safer resolve/save logic to persist directories even if a selected file is invalid.
- dlclivegui/gui/main_window.py: use the new ModelPathStore APIs to choose a better start directory, preselect the last used model, persist the selected directory, and update config; also rename a tile-related member to reflect it holds scale.
- dlclivegui/utils/display.py: extract compute_tiling_geometry() to compute rows/cols and tile sizes (based on a reference frame) and make create_tiled_frame() reuse it; update compute_tile_info() to use the same geometry so overlay offsets/scales match the tiled view; add empty-frame guards and minor robustness improvements.

These changes fix mismatches between the tiled display and overlay transforms, make the model browser more user-friendly, and harden path handling.
Clear previous list items before populating scan results, add a non-selectable "No cameras detected." placeholder when no cameras are found, and select the first detected camera by default after a successful scan. Also returns early when showing the placeholder to avoid adding extra items.
@C-Achard C-Achard marked this pull request as draft February 3, 2026 15:41
Refactor processor discovery and harden socket-based processors.

- GUI (main_window.py): use packaged default processors dir for the processor path, let Browse default to that location, and refresh the processor dropdown by scanning a user-selected folder or falling back to the bundled dlclivegui.processors package. Simplified combo population and status messaging.

- Processors (dlc_processor_socket.py): major cleanup and robustness improvements for socket-based processors: introduce a PROCESSOR_REGISTRY with register_processor decorator, rename processor classes to CamelCase, set socket timeouts, add safe connection/listener close helpers, separate accept/receive loops, improve stop/cleanup logic (including Windows-friendly delays), improve broadcast handling, and make save/get_data more robust. Also added small API improvements (filter_kwargs defaulting to None -> {}). Concrete processors are registered via @register_processor.

- Utilities (processor_utils.py): add default_processors_dir() to locate packaged processors, implement scan_processor_package() to discover processors within the package namespace, and rework load_processors_from_file() for safer, isolated module imports with better logging and error handling. scan_processor_folder now uses the updated loader and logs exceptions via logger.

Notes: this changes some class/registry names and processor keys (e.g. class names are now MyProcessorSocket / MyProcessorTorchmodelsSocket), so consumers instantiating processors by old names should update to the new registry keys. Error handling and logging have been added to aid debugging of import/load failures.
Add new unit tests for processor utilities and the BaseProcessorSocket: tests/custom_processors/test_base_processor.py (tests BaseProcessorSocket init/stop, recording flags, process behavior, broadcasting error handling) and tests/custom_processors/test_builtin_discovery_utils.py (tests scanning/loading/instantiation/display of processor modules). Also tweak tests/services/test_dlc_processor.py to increase qtbot.waitUntil timeout from 1500 to 5000ms with a comment to reduce flakiness due to scheduling delays.
Delete dlclivegui/cameras/config_adapters.py which contained the ensure_dc_camera helper that normalized CameraSettings from a dataclass, Pydantic CameraSettingsModel, or dict. This removes redundant/unused adapter code as part of a config cleanup/restructure; normalization logic is no longer required in this location.
Introduce a reusable splash screen implementation and integrate it into the app entrypoint. Added dlclivegui/gui/misc/splash.py with SplashConfig, build_splash_pixmap and show_splash (handles missing images with a filled fallback pixmap). Exposed splash-related constants in theme.py and updated main.py to use SplashConfig/show_splash, keep a reference to the main window on the app object, and use the QApplication attribute enum member for HiDPI. Added tests/tests/gui/test_app_entrypoint.py to mock Qt classes and verify both valid-image and fallback splash behavior, timer handling, and that the main window is shown.
Comment out explicit QApplication.setAttribute(A A_UseHighDpiPixmaps) because Qt6 enables HiDPI pixmaps by default, so the attribute no longer needs to be set.
If the splash image is invalid or missing, build_splash_pixmap now returns None (disabling the splash) and show_splash returns None accordingly. main() is updated to guard splash.close() when no splash was created. Tests updated to reflect the new splash API: test_app_entrypoint rewritten to use the centralized show_splash mock and renamed tests, a new test_splash_screen module verifies build_splash_pixmap valid/fallback behavior, and a small qtbot.wait(5) was added to test_dlc_processor to reduce flakiness.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants